<?php
require('index.class.php');

abstract class Cacher{
	abstract function set ($key, $value , $expiration=0);
	abstract function has($key);
	abstract function get($key);
	abstract function delete($key);
}


class DiskCacher extends Cacher{
	
	const shmIndexMaxMemory=1073741824;
	const lazyDefragmentatorKey=1;
	const lazyDefragmentatorBuffer=10485760;//должен быть больше чем самый большой элемент в кэше
	
	const deletedFileMaxProcessing=1048576;
	
	const maxReaders=10;
	public $clear=true;
	
	const lazyDefragmentTiming=20000;
	
	private $dataFile;
	private $deletedFile;
	
	//private $shmIndex;
	public $shmIndex;
	private $shmSystem;
	
	private $semRead;
	private $semWrite;
	private $semShm;
	private $semShmSystem;
	private $index;
	
	function __construct($clear) {
		$this->clear=$clear;
		if($clear)
			$this->clear();
		$this->dataFile=fopen('./dataFile','a+');
		//fclose($this->dataFile);
		//$this->dataFile=fopen('./dataFile','r+');
		if($this->dataFile===false)
			Throw new ErrorException('dataFile acces problem'); 

		$this->deletedFile=fopen('./deletedFile','a+');
		if($this->deletedFile===false)
			Throw new ErrorException('deletedFile acces problem');
			
		$IPCkeyShmIndex=ftok(__FILE__, 'z');
		if($IPCkeyShmIndex===-1)
			Throw new ErrorException('sharedMemory (index) IPC key create problem');		
		$this->shmIndex=shmop_open($IPCkeyShmIndex, "c", 0644, self::shmIndexMaxMemory);
		if($this->shmIndex===false)
			Throw new ErrorException('sharedMemory attach problem');		
			

			
		$IPCkeyShmSystem=ftok(__FILE__, 's');
		if($IPCkeyShmSystem===-1)
			Throw new ErrorException('sharedMemory (system) IPC key create problem');
		$this->shmSystem=shm_attach($IPCkeyShmSystem,10240);
		if ($this->shmSystem=== false) 
			Throw new ErrorException('sharedMemory (system) problem');

		
		
		$IPCkeySemRead=ftok(__FILE__, 'a');
		if($IPCkeySemRead===-1)
			Throw new ErrorException('semaphore (semRead) IPC key  create problem');		
		$this->semRead=sem_get  ( $IPCkeySemRead, self::maxReaders);
		if ($this->semRead=== false) 
			Throw new ErrorException('semaphore (semRead) attach problem');
			
		$IPCkeySemWrite=ftok(__FILE__, 'b');
		if($IPCkeySemWrite===-1)
			Throw new ErrorException('semaphore (semWrite) IPC key  create problem');				
		$this->semWrite=sem_get  ( $IPCkeySemWrite, 1);
		if ($this->semWrite=== false) 
			Throw new ErrorException('semaphore (semWrite) attach problem');
				
		$IPCkeySemShm=ftok(__FILE__, 'd');
		if($IPCkeySemShm===-1)
			Throw new ErrorException('semaphore (semShm) IPC key  create problem');				
		$this->semShm=sem_get  ( $IPCkeySemShm, 1);
		if ($this->semShm=== false) 
			Throw new ErrorException('semaphore (semShm) attach problem');

		$IPCkeySemShmSystem=ftok(__FILE__, 'e');
		if($IPCkeySemShmSystem===-1)
			Throw new ErrorException('semaphore (semShmSystem) IPC key  create problem');				
		$this->semShmSystem=sem_get  ( $IPCkeySemShmSystem, 1);
		if ($this->semShmSystem=== false) 
			Throw new ErrorException('semaphore (semShmSystem) attach problem');
		
		$this->index=new Index($this->shmIndex,$this->shmSystem,$this->semShmSystem,$clear);
		
		sem_acquire($this->semShmSystem);
		if(!shm_has_var($this->shmSystem,self::lazyDefragmentatorKey))
			shm_put_var($this->shmSystem,self::lazyDefragmentatorKey,false);
		sem_release($this->semShmSystem);

	//дебаг, удалить
	//shm_remove_var($this->shmIndex,1);
	//shm_remove_var($this->shmIndex,2);
}


	
	function waitOperationPermission (){
		
		//var_dump(shm_get_var($this->shmSystem,self::lazyDefragmentatorKey));
		//exit(123);
		
		$permission=false;
		while($permission===false){
			sem_acquire($this->semShmSystem);
			$permission=!(shm_get_var($this->shmSystem,self::lazyDefragmentatorKey));
			sem_release($this->semShmSystem);
			//var_dump($permission);
			if($permission)
				return true;
			usleep(1000000);
		}
	}
	function set ($key, $value , $expiration=0){
		$this->waitOperationPermission();
		sem_acquire($this->semWrite);
		$key=intval($key);
		fseek ($this->dataFile,0, SEEK_END);
		$currentPosition=ftell($this->dataFile);
		$value=serialize($value);
		$length=12+mb_strlen($value, '8bit');
		$expiration=mktime()+$expiration;
		$data=pack('i',$key).pack('i',$length).pack('i',$expiration).$value;
		fwrite($this->dataFile,$data);
		
		$oldPosition=false;
		sem_acquire($this->semShm);
			$oldPosition=$this->index->get($key);
			$this->index->set($key,$currentPosition);
		sem_release($this->semShm);
		
		if($oldPosition!==false){
				fseek ($this->deletedFile,0, SEEK_END);
				fwrite($this->deletedFile,pack('i',$oldPosition));

		}
		sem_release($this->semWrite);
	}
	
	function get($key){
		$this->waitOperationPermission();
		sem_acquire($this->semRead);
		$key=intval($key);
		
		sem_acquire($this->semShm);
		$position=$this->index->get($key);
		if($position===false){
			sem_release($this->semShm);
			sem_release($this->semRead);
			return false;
		}
		sem_release($this->semShm);
		
		//var_dump($position);
		
		fseek ($this->dataFile,$position);
		
		$head=fread($this->dataFile,8);
		$data=unpack('ikey/ilength',$head);
		
		fseek($this->dataFile,$position+12);
		$body=fread($this->dataFile,$data['length']-12);
		$value=unserialize($body);
		sem_release($this->semRead);
		return $value;
	}
	
	function has($key){
	
	}	
	function delete($key){
		$this->waitOperationPermission();
		sem_acquire($this->semWrite);
		$key=intval($key);
		
		$oldPosition=false;
		sem_acquire($this->semShm);
		$oldPosition=$this->index->get($key);
		if($oldPosition){
			$this->index->delete($key);
		}
		sem_release($this->semShm);
		
		if($oldPosition!==false){
			fseek ($this->deletedFile,0, SEEK_END);
			fwrite($this->deletedFile,pack('i',$oldPosition));
		}
		sem_release($this->semWrite);
	}
	function lazyDefragmentLock(){
		sem_acquire($this->semShmSystem);
		if(!shm_has_var($this->shmSystem,self::lazyDefragmentatorKey))
			shm_put_var($this->shmSystem,self::lazyDefragmentatorKey,true);
		sem_release($this->semShmSystem);
		sem_acquire($this->semWrite);
		for($i=0;$i<self::maxReaders;$i++)
			sem_acquire($this->semRead);
	}
	function lazyDefragmentUnlock(){
		sem_acquire($this->semShmSystem);
		if(!shm_has_var($this->shmSystem,self::lazyDefragmentatorKey))
			shm_put_var($this->shmSystem,self::lazyDefragmentatorKey,false);
		sem_release($this->semShmSystem);
		sem_release($this->semWrite);
		for($i=0;$i<self::maxReaders;$i++)
			sem_release($this->semRead);
	}
	function lazyDefragment(){
		fclose($this->dataFile);
		$this->dataFile=fopen('./dataFile','c+');
		$this->lazyDefragmentLock();
		fseek ($this->deletedFile,0);
		//Problem!!!
		$deletedBinary=fread($this->deletedFile,self::deletedFileMaxProcessing);
		$deletedBinaryLength=intval(mb_strlen($deletedBinary, '8bit')/4);
		$deleted=Array();
		
		for ($i=0;$i<$deletedBinaryLength;$i++){
			$deletedOne=unpack('iseg',mb_substr($deletedBinary,$i*4,4,'8bit'));
			$deleted[]=$deletedOne['seg'];
		}
		sort($deleted,SORT_NUMERIC);
		//fseek ($this->deletedFile,0);
		//расскомментить!
		ftruncate($this->deletedFile,0);
		$this->lazyDefragmentUnlock();
		var_dump($deleted);
		if(count($deleted)<1)
			return true;
		
		$curentElement=0;
		$curentPosition=$deleted[$curentElement];
		
		while($curentElement<count($deleted)){
			$curentElementPosition=$deleted[$curentElement];
			fseek ($this->dataFile,$curentElementPosition+4);		
			$head=fread($this->dataFile,4);
			$head=unpack('ilength',$head);
			$length=$head['length'];
			
			$startBlock=$curentElementPosition+$length;

			if($curentElement<count($deleted)-1)
				$endBlock=$deleted[$curentElement+1];
			else
				$endBlock=null;
				//echo '123';
			var_dump('offset',$startBlock,$endBlock,$curentPosition);
			$endBlock=$this->shiftFileBlock($startBlock,$endBlock,$curentPosition);
			$curentPosition+=($startBlock-$endBlock);
			$curentElement++;
			
			if($curentElement<count($deleted))
				usleep(self::lazyDefragmentTiming);
		}
		fclose($this->dataFile);
		$this->dataFile=fopen('./dataFile','a+');		
	}
	function shiftFileBlock($startBlock,$endBlock,$toPosition){
		$curBlock=$startBlock;
		$offset=$startBlock-$toPosition;
		$endBlockCheck=false;
		if($endBlock===null){
			fseek ($this->dataFile,0, SEEK_END);
			$endBlock=ftell($this->dataFile);
			$endBlockCheck=true;
		}
		//var_dump('IN',$curBlock,$endBlock);

		while($curBlock<=$endBlock){

			$this->lazyDefragmentLock();
			if($endBlockCheck===true){
				fseek ($this->dataFile,0, SEEK_END);
				$endBlock=ftell($this->dataFile);
			}
			//var_dump('1',$curBlock,$endBlock,$toPosition);
//			$keys4offset=Array();
			$newBufferSize=0;
			
			$bufferSize=$endBlock-$curBlock;
			$bufferSize=$bufferSize>self::lazyDefragmentatorBuffer?self::lazyDefragmentatorBuffer:$bufferSize;
			//var_dump('1',$curBlock,$endBlock,$toPosition,$bufferSize);
			if($bufferSize>0){
				fseek ($this->dataFile,$curBlock);
				$buffer=fread($this->dataFile,$bufferSize);
				$bufferPosition=0;
				while(true){
					if ($bufferPosition+8>$bufferSize)
						break;					
					$head=mb_substr($buffer, $bufferPosition, 8, '8bit');
					$head=unpack('ikey/ilength',$head);
					//$keys4offset[]=ikey
					$key=$head['key'];
					$length=$head['length'];
					if ($bufferPosition+$length>$bufferSize)
						break;				
					var_dump('3',$key);
					sem_acquire($this->semShm);
						$oldPosition=$this->index->get($key);
						if($oldPosition){
							if($oldPosition===$bufferPosition)
								//дебаг, раскоментить
								$this->index->delete($key);
								$this->index->set($key,$toPosition+$bufferPosition);
								//var_dump('new offset',$key,$toPosition+$bufferPosition,$oldPosition-$offset,$this->index->get($key));
						}
					sem_release($this->semShm);
					$bufferPosition+=$length;
					var_dump('4',$bufferPosition);
				}
				//var_dump('5',$toPosition,$buffer,$bufferPosition);
				fseek ($this->dataFile,$toPosition);
				//var_dump('6',ftell($this->dataFile));
				//дебаг, раскоментить
				fwrite($this->dataFile,$buffer,$bufferPosition);
				//var_dump('8',fwrite($this->dataFile,'eee'));
				//fflush($this->dataFile);
				$curBlock+=$bufferPosition;
				$toPosition+=$bufferPosition;
				//truncate fil'a
			}
			//var_dump('7',$curBlock>=$endBlock);
			if($endBlockCheck && $curBlock>=$endBlock){
				//var_dump('truncate',$toPosition);
				//дебаг, раскоментить
				fflush($this->dataFile);
				ftruncate($this->dataFile,$toPosition);
			}
			$this->lazyDefragmentUnlock();
			if($curBlock<$endBlock)
				usleep(self::lazyDefragmentTiming);
			else
				break;
		}
		return $endBlock;		
	}
	function clear() {
		unlink('./dataFile');unlink('./deletedFile');
	}
	function __destruct() {
       
   }
}
?>